import datetime
import json
import re

from flask import request, jsonify, g
from bson.objectid import ObjectId

from app.libs.redprint import RedPrint
from app.auth import permission_required
from app.db.models.users import User
from app.db.models.jobs import Job
from app.db.models.url_blacklist import UrlBlackList
from app.db.models.url_blacklist_settings import UrlBlackListSettings
from app.db.models.tasks import Task
from app.db.models.balances import Balances
from app.db.models.defense_targets import DefenseTargets
from app.libs.oms import Client
from app.libs.utils import get_job, get_url_path_params
from app.config.settings import SCANNER_IP, DATETIME_FMT, UTC_PREFIX
from app.errors import ParameterError, JobNotSyncYAQError
from app.handler.v2_cloud import (
    UdbGetBlacklist, UdbGetBlackListSettings, ScanUrlBlackList, ScanUrlBlackListSetting, Send)
from app.validators.url_blacklist_setting import UrlBlackListSetAddForm, UrlBlackListSetEditForm
from app.validators.cloud import DefenseSourceCheckForm
from app.log.logs import CCloudLog, DCloudLog
from app.libs.utility import filter_cha
from app.libs.regexp import IP_RE

api = RedPrint('cloud')


def _get_username_dict(query=None):
    query = query or {}

    user_dict = {}
    for _user in User.objects.find(query):
        user_dict[_user.id] = _user.username

    return user_dict


@api.route('/idw_get_assets', methods=['GET'])
def idw_get_assets():
    request_args = request.args
    uid = request_args.get('uid')
    username = filter_cha(request_args.get('username', ''))
    target_name = filter_cha(request_args.get('targetName', ''))
    target_url = filter_cha(request_args.get('targetUrl', ''))
    search_key = filter_cha(request_args.get('searchKey', ''))
    page = int(request_args.get('page', 1))
    limit = int(request_args.get('limit', 10))

    ctx = {
        'code': 200,
        'msg': 'ok',
        'results': []
    }

    scanner_ipv4 = []
    scanner_ipv6 = []

    for item in SCANNER_IP:
        if re.match(IP_RE, item):
            scanner_ipv4.append(item)
        else:
            scanner_ipv6.append(item)

    user_dict = {}
    user_query = {}
    job_query = {'verification.isVerified': True}

    if search_key:
        _or = [{'targetUrl': {'$regex': search_key}}, {'note': {'$regex': search_key}}]
        job_query['$or'] = _or

    if g.role.name in ['普通用户']:
        user_dict[g.user.id] = g.user.username
        job_query['uid'] = g.user.id
        operator = g.user.username
    else:
        operator = request_args.get('operator')
        if username:
            user_query['username'] = {'$regex': username}
        if uid:
            user_query['_id'] = ObjectId(uid)
        if target_name:
            job_query['note'] = {'$regex': target_name}
        if target_url:
            job_query['targetUrl'] = {'$regex': target_url}

        if not user_query and len(job_query.keys()) < 2:
            results = {
                'not_added': [],
                'assets': [],
                'count': 0,
                'scanner_ipv4': scanner_ipv4,
                'scanner_ipv6': scanner_ipv6
            }
            ctx['results'] = results
            return jsonify(ctx)
        user_dict.update(_get_username_dict(user_query))
        job_query['uid'] = {'$in': list(user_dict.keys())}

    job_list = []
    exists_domain = []
    for job in Job.objects.find(job_query):
        if job.targetUrl in exists_domain:
            continue
        job_list.append({
            'name': job.note,
            'targetUrl': job.targetUrl,
            'username': user_dict.get(job.uid, ''),
            'createTime': job.createTime.strftime(DATETIME_FMT),
            '_id': str(job.id)
        })
        exists_domain.append(job.targetUrl)

    not_added_jobs = []
    added_jobs = []
    if job_list:
        params = {
            'assets': job_list,
            'ips': SCANNER_IP,
            'operator': operator
        }

        idw_jobs = Client().post('/api/inner/dnss/scanner_ip/list/', json.dumps(params)).get('ret', {})

        added_jobs = idw_jobs.get('added', [])
        not_added_jobs = idw_jobs.get('not_added', [])

    results = {
        'not_added': not_added_jobs,
        'assets': added_jobs[(page-1)*limit:page*limit],
        'count': len(added_jobs),
        'scanner_ipv4': scanner_ipv4,
        'scanner_ipv6': scanner_ipv6
    }

    ctx['results'] = results

    return jsonify(ctx)


@api.route('/idw_action', methods=['POST'])
def idw_action():
    """
    Add or delete ip from whitelist.
    """
    request_args = request.form or json.loads(request.data.decode())

    action = request_args.get('action')
    asset_id_list = request_args.get('assetIds', [])

    query = {'verification.isVerified': True}
    if g.role.name in ['普通用户']:
        query['uid'] = g.user.id
        operator = g.user.username
    else:
        operator = request_args.get('operator')

    oms = Client()
    if action == 'add':
        send_method = oms.post
    elif action == 'delete':
        send_method = oms.delete
    else:
        raise ParameterError(msg="'action' error.")

    asset_id_list = json.loads(asset_id_list) if isinstance(asset_id_list, str) else asset_id_list
    query['_id'] = {'$in': [ObjectId(_id) for _id in asset_id_list]}

    user_dict = _get_username_dict()

    target_list = []

    for _job in Job.objects.find(query):
        target_list.append({
            'name': _job.note,
            'username': user_dict.get(_job.uid, ''),
            'targetUrl': _job.targetUrl
        })

    if target_list:
        params = {
            'assets': target_list,
            'ips': SCANNER_IP,
            'operator': operator
        }

        resp = send_method('/api/inner/dnss/scanner_ip/', json.dumps(params)).get('ret', {})
    else:
        resp = {}

    results = {
        'info': resp.get('data', []),
        'total': resp.get('total_count', 0),
        'success': resp.get('success_count', 0),
        'failed': resp.get('fail_count', 0),
    }
    # operate log
    if (count := len(results['info'])) == 1 and action == 'add':
        asset = results['info'][0]
        status_msg = '成功' if asset.get('status') == 'success' else f"失败，失败原因：{asset.get('msg', '')}"
        CCloudLog().info(
            action_message='新增扫描器IP白名单',
            detail=f"资产名称：{asset.get('name', '')}，资产地址：{asset.get('targetUrl', '')} ,添加扫描器出口IP白名单，状态：{status_msg}"
        )
    elif count == 1 and action == 'delete':
        asset = results['info'][0]
        status_msg = '成功' if asset.get('status') == 'success' else f"失败，失败原因：{asset.get('msg', '')}"
        DCloudLog().info(
            action_message='删除扫描器IP白名单',
            detail=f"资产名称：{asset.get('name', '')}，资产地址：{asset.get('targetUrl', '')} ,删除扫描器出口IP白名单，状态：{status_msg}"
        )
    elif count > 1 and action == 'add':
        CCloudLog().info(
            action_message='新增扫描器IP白名单',
            detail=f"一键添加扫描器出口IP白名单，成功{results.get('success', 0)}个，失败{results.get('failed', 0)}个"
        )
    elif count > 1 and action == 'delete':
        DCloudLog().info(
            action_message='删除扫描器IP白名单',
            detail=f"批量删除扫描器出口IP白名单，成功{results.get('success', 0)}个，失败{results.get('failed', 0)}个"
        )

    return jsonify({'code': 200, 'msg': 'ok', 'results': results})


@api.route('/cyd_enable/jobs', methods=['GET'])
def cyd_enable_jobs():
    request_args = request.args
    if g.role.name in ['普通用户']:
        uid = g.user.id
    else:
        uid = request_args.get('uid')
    data = []
    exists_domain = []
    query = {"cydEnabled": True, "verification__isVerified": True}
    if uid:
        query.update({"uid": uid})
    for j in Job.objects.filter(**query).only("id", "note", "targetUrl"):
        if j.targetUrl in exists_domain:
            continue
        data.append({"job_id": j.id, "note": j.note or "", "targetUrl": j.targetUrl})
        exists_domain.append(j.targetUrl)
    return jsonify({"status": "success", "code": 200, "result": data})


@api.route('/udb_get_blacklist', methods=['GET'])
def udb_get_blacklist():
    return jsonify({'code': 200, 'msg': 'ok', 'results': UdbGetBlacklist().get()})


@api.route('/udb_get_blacklist_set', methods=['GET'])
def udb_get_blacklist_set():
    return jsonify({'code': 200, 'msg': 'ok', 'results': UdbGetBlackListSettings().get()})


@api.route('/udb_add_blacklist', methods=['POST'])
def udb_add_blacklist():
    """
    Add task result  to blacklist
    """
    request_args = request.form or json.loads(request.data.decode())
    task_id = request_args.get('taskId')
    warning_id = request_args.get('warningId')
    uid = request_args.get('uid', '')
    code = 200
    message = '添加成功'

    if not task_id or not warning_id:
        return jsonify({'code': 400, 'msg': '参数错误'})

    if g.role.name == "普通用户":
        ouid = g.user.id
        operator = g.user.username
    else:
        ouid = ObjectId(uid)
        operator = request_args.get("operator", '')
    user = User.objects.get(pk=ouid)
    task = Task.objects.only(
        'endTime', 'taskType', 'jobId', 'uid', 'result', 'taskType').filter(
        pk=ObjectId(task_id), uid=ouid).first()
    if not task:
        return jsonify({'code': 400, 'msg': '任务不存在'})

    url = None
    title = ''
    level_name = ''
    msg_num = 0
    for warning in task.result.warnings:
        if str(getattr(warning, '_id', 'asdqhwbjdhqb')) == warning_id:
            url = warning.affects
            title = warning.title
            level_name = warning.levelName
            warning_detail = warning.detail
            msg_num = len(warning_detail)
            break
    if url is None:
        return jsonify({'code': 400, 'msg': '告警不存在'})

    asset = Job.objects.only('targetUrl').filter(pk=task.jobId).first()

    if not asset or not (job := get_job(asset.id)):
        raise JobNotSyncYAQError(msg="添加失败，资产不存在或未同步至云安全")

    ex_url = get_url_path_params(url)

    params = {
        'username': user.username,
        'dns_id': job.dns_id,
        'site_id': job.site_id,
        'blacklist': [ex_url],
        'operator': operator
    }
    resp_data = Client().post('/api/inner/dnss/udb_add_blacklist/', json.dumps(params)).get('data', {})
    failed = resp_data.get('failed', [])

    if not failed:
        msg = ''
        if task.taskType == 'vul':
            msg = f"1个{level_name}漏洞"
        elif task.taskType == 'securityEvent':
            msg = f'{msg_num}个{title}'

        prefix_end_time = task.endTime + UTC_PREFIX
        detail = f"{url}页面在{prefix_end_time.strftime(DATETIME_FMT)}监测发现存在{msg}，已自定义加黑该风险URL。"
        obj_blacklist = UrlBlackList.objects.filter(url=ex_url, job_id=job.id).first()
        if obj_blacklist:
            message = "该风险地址已添加至云防御URL黑名单中"
            code = 400
            return jsonify({'code': code, 'msg': message})
        else:
            UrlBlackList(
                url=ex_url,
                create_time=datetime.datetime.utcnow(),
                add_type='自定义',
                job_id=task.jobId,
                task_id=task.id,
                uid=ouid,
                detail=detail,
                task_type=task.taskType
            ).save()
    else:
        code = 400
        message = "URL黑名单超出上限"

    # logger
    CCloudLog().info(
        action_message='添加URL黑名单',
        detail=f"自定义添加URL黑名单，成功{1-len(failed)}个，失败{len(failed)}个"
    )

    return jsonify({'code': code, 'msg': message})


def udb_del_normal(request_args, model, id_name):
    ids = request_args.get(id_name)
    ids = [ObjectId(_id) for _id in ids]

    if g.role.name == '普通用户':
        obj_model_list = model.objects.filter(pk__in=ids, uid=g.user.id)
    else:
        obj_model_list = model.objects.filter(pk__in=ids)

    # if len(obj_model_list) != len(ids):
    #     return {'code': 400, 'msg': 'id error.'}

    num = len(obj_model_list)
    asset_id_list = []
    if model == UrlBlackList:
        model_name = "URL黑名单"
        job_map = {}
        dns_url_map = {}
        for obj in obj_model_list:
            if not (_job := job_map.get(obj.job_id)):
                _job = get_job(obj.job_id)
                job_map[obj.job_id] = _job
            if _job:
                dns_url_map.setdefault(f"{_job.dns_id}_{_job.site_id}", []).append(obj.url)

        if g.role.name == '普通用户':
            operator = g.user.username
        else:
            operator = request_args.get('operator', '')

        params = {"urlBlacklist": dns_url_map, "operator": operator}
        Client().post("/api/inner/dnss/udb_sync_del_blacklist/", json.dumps(params))

        for obj in obj_model_list:
            asset_id_list.append(str(obj.job_id))
            obj.delete()
    else:
        model_name = "策略"
        for obj in obj_model_list:
            asset_id_list.append(str(obj.job_id))
            obj.delete()
            balance = Balances.objects.filter(uid=obj.uid).first()
            balance.update(inc__balance__urlBLSMaxNum__used=-1)

    results = {
        'assetIds': asset_id_list
    }

    detail = f"批量删除{model_name}，成功{num}个，失败0个"
    DCloudLog().info(
        action_message='删除扫描器IP白名单',
        detail=detail
    )

    return {'code': 200, 'msg': '删除成功', 'results': results}


@api.route('/udb_del_blacklist', methods=['POST'])
def udb_del_blacklist(model=UrlBlackList, id_name="blacklistIds"):
    request_args = json.loads(request.data.decode())
    ctx = udb_del_normal(request_args, model, id_name)
    return jsonify(ctx)


@api.route('/udb_edit_blacklist_set', methods=['POST'])
def udb_edit_blacklist_set():
    request_args = json.loads(request.data)

    if setting_id := request_args.get('settingId', ''):
        if g.role.name == '普通用户':
            setting = UrlBlackListSettings.objects.filter(pk=ObjectId(setting_id), uid=g.user.id).first()
        else:
            setting = UrlBlackListSettings.objects.filter(pk=ObjectId(setting_id)).first()

        request_args['uid'] = setting.uid
        form = UrlBlackListSetEditForm(request_args).validate_for_api()
        if not setting:
            return jsonify({'code': 400, 'msg': "策略不存在或已被删除"})
        setting.update(**form.data)
        action = "修改"
    else:
        if g.role.name == '普通用户':
            uid = g.user.id
        else:
            uid = request_args.get('uid', '')

        request_args['uid'] = uid
        form = UrlBlackListSetAddForm(request_args).validate_for_api()

        balance = Balances.objects.filter(uid=uid).first()
        balance_content = balance.balance

        if balance_content.urlBLSMaxNum.used >= balance_content.urlBLSMaxNum.total:
            return jsonify({'code': 400, 'msg': f"当前策略数量已超过{balance_content.urlBLSMaxNum.total}条上限"})

        UrlBlackListSettings(**form.data).save()
        balance.update(inc__balance__urlBLSMaxNum__used=1)
        action = "添加"

    CCloudLog().info(
        action_message=f'{action}策略',
        detail=f"{action}策略，成功1个，失败0个"
    )

    return jsonify({'code': 200, 'msg': f"{action}成功"})


@api.route('/udb_del_blacklist_set', methods=['POST'])
def udb_del_blacklist_set(model=UrlBlackListSettings, id_name="settingIds"):
    request_args = json.loads(request.data.decode())
    ctx = udb_del_normal(request_args, model, id_name)
    return jsonify(ctx)


@api.route('/udb_del_all', methods=['POST'])
def udb_del_all():
    request_args = json.loads(request.data.decode())
    job_id = request_args.get('job_id')

    if g.role.name == '普通用户':
        uid = g.user.id
        operator = g.user.username
    else:
        uid = ObjectId(request_args.get('uid', ''))
        operator = request_args.get('operator', 'scanv')

    job = get_job(job_id)
    if not job:
        for obj in UrlBlackList.objects.filter(uid=uid, job_id=job_id):
            obj.delete()
        for obj in UrlBlackListSettings.objects.filter(uid=uid, job_id=job_id):
            obj.delete()
    else:
        obj_url_blacklists = [obj for obj in UrlBlackList.objects.filter(uid=uid, job_id=job_id)]
        obj_url_blacklist_sets = [obj for obj in UrlBlackListSettings.objects.filter(uid=uid, job_id=job_id)]
        client = Client()

        dns_url_map = {}
        for obj in obj_url_blacklists:
            dns_url_map.setdefault(f"{job.dns_id}_{job.site_id}", []).append(obj.url)
        params = {"urlBlacklist": dns_url_map, "operator": operator}
        client.post("/api/inner/dnss/udb_sync_del_blacklist/", json.dumps(params))
        for obj in obj_url_blacklists:
            obj.delete()
        for obj in obj_url_blacklist_sets:
            obj.delete()

    DCloudLog().info(
        action_message=f'删除资产{job.note}全部URL黑名单和策略。',
        detail=f"删除资产{job.note}全部URL黑名单和策略。"
    )

    return jsonify({'code': 200, 'msg': 'ok'})


@api.route('/udb_system_add_blacklist', methods=['POST'])
@permission_required('admin')
def udb_system_add_blacklist():
    request_args = request.form or json.loads(request.data)
    task_ids = request_args.get('taskIds', [])
    task_ids = [ObjectId(_id) for _id in task_ids]
    system_add_black_list(task_ids)

    return jsonify({'code': 200, 'msg': 'ok'})


def system_add_black_list(task_ids):
    warning_list = []
    uid_list = []
    asset_id_list = []
    for task in Task.objects.find({'_id': {'$in': task_ids}}):
        warning_list.append({
            'uid': task.uid,
            'job_id': task.jobId,
            'task_type': task.taskType,
            'end_time': task.endTime,
            'task_id': task.id,
            'warnings': task.result.warnings
        })
        uid_list.append(task.uid)
        asset_id_list.append(task.jobId)

    uid_user_map = {item.id: item for item in User.objects.find({'_id': {'$in': uid_list}})}
    aid_asset_map = {item.id: item for item in Job.objects.find({'_id': {'$in': asset_id_list}})}

    scan_blacklist = ScanUrlBlackList(UrlBlackList.objects)
    scan_blacklist_set = ScanUrlBlackListSetting(UrlBlackListSettings.objects)
    send = Send()

    uid_setting_map = scan_blacklist_set.get_uid_setting_map(uid_list)
    scan_blacklist.set_warnings(warning_list)
    scan_blacklist.set_uid_settings_map(uid_setting_map)
    scan_blacklist.set_uid_user_map(uid_user_map)
    scan_blacklist.set_aid_asset_map(aid_asset_map)
    scan_blacklist.travel()

    scan_blacklist.save()

    update_dict = scan_blacklist.get_update_dict()
    send.set_update_dict(update_dict)
    send.send_to_alert_group()

    # logger
    for _, _update_dict_item in update_dict.items():
        _s_num = len(_update_dict_item.get('success_list', []))
        _f_num = len(_update_dict_item.get('failed_list', []))
        CCloudLog().info(
            action_message='添加URL黑名单',
            detail=f"系统添加URL黑名单，成功{_s_num}个，失败{_f_num}个"
        )


@api.route('/site_check', methods=['POST'])
@permission_required('admin')
def site_check():
    """云防御源站安全检测"""
    form = DefenseSourceCheckForm().validate_for_api()
    return jsonify({'code': 200, 'msg': 'success'})


@api.route('/task/get', methods=['GET'])
def scanv_check_task_list():
    request_args = request.args
    page = int(request_args.get('page', 1))
    limit = int(request_args.get('limit', 10))

    query = {}
    if g.role.name == '普通用户':
        query['user_id'] = g.user.id

    defense_target_list = DefenseTargets.objects.find(query).order_by("-createAt")
    data = [item for item in defense_target_list]

    return jsonify({'code': 200, 'status': 'success', 'results': data})
